/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * Fory C++ Row Format Example
 *
 * This example demonstrates how to use Fory's row format for
 * cache-friendly binary random access. Row format is ideal for
 * scenarios where you need:
 * - Partial serialization/deserialization
 * - Random field access without full deserialization
 * - Interoperability with columnar formats
 */

#include <cstdint>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include "fory/encoder/row_encode_trait.h"
#include "fory/encoder/row_encoder.h"
#include "fory/row/row.h"
#include "fory/row/schema.h"
#include "fory/row/writer.h"

// Define a struct for the encoder example
struct Employee {
  std::string name;
  int32_t id;
  float salary;
};

// Register field info for automatic encoding
FORY_FIELD_INFO(Employee, name, id, salary);

// Define a nested struct
struct Department {
  std::string dept_name;
  Employee manager;
  std::vector<Employee> employees;
};

FORY_FIELD_INFO(Department, dept_name, manager, employees);

int main() {
  std::cout << "=== Fory C++ Row Format Example ===" << std::endl << std::endl;

  // ============================================================================
  // Example 1: Manual Row Writing with Schema
  // ============================================================================
  std::cout << "--- Example 1: Manual Row Writing ---" << std::endl;
  {
    using namespace fory::row;

    // Define schema with fields
    auto name_field = field("name", utf8());
    auto age_field = field("age", int32());
    auto score_field = field("score", float32());
    auto tags_field = field("tags", list(utf8()));

    std::vector<FieldPtr> fields = {name_field, age_field, score_field,
                                    tags_field};
    auto row_schema = schema(fields);

    // Create a row writer
    RowWriter writer(row_schema);
    writer.Reset();

    // Write primitive fields
    writer.WriteString(0, "Alice");
    writer.Write(1, static_cast<int32_t>(25));
    writer.Write(2, 95.5f);

    // Write array field
    writer.SetNotNullAt(3);
    int array_start = writer.cursor();
    auto list_type = std::dynamic_pointer_cast<ListType>(utf8());
    ArrayWriter array_writer(list(utf8()), &writer);
    array_writer.Reset(3);
    array_writer.WriteString(0, "developer");
    array_writer.WriteString(1, "team-lead");
    array_writer.WriteString(2, "mentor");
    writer.SetOffsetAndSize(3, array_start, writer.cursor() - array_start);

    // Convert to row and read back
    auto row = writer.ToRow();

    std::cout << "Written row:" << std::endl;
    std::cout << "  name: " << row->GetString(0) << std::endl;
    std::cout << "  age: " << row->GetInt32(1) << std::endl;
    std::cout << "  score: " << row->GetFloat(2) << std::endl;

    auto tags_array = row->GetArray(3);
    std::cout << "  tags: [";
    for (int i = 0; i < tags_array->num_elements(); ++i) {
      std::cout << tags_array->GetString(i);
      if (i < tags_array->num_elements() - 1)
        std::cout << ", ";
    }
    std::cout << "]" << std::endl;

    // Print full row string representation
    std::cout << "  Full row: " << row->ToString() << std::endl;
  }
  std::cout << std::endl;

  // ============================================================================
  // Example 2: Using RowEncoder for Automatic Encoding
  // ============================================================================
  std::cout << "--- Example 2: Automatic Encoding with RowEncoder ---"
            << std::endl;
  {
    using namespace fory::row;

    // Create an employee
    Employee emp{"Bob", 1001, 75000.0f};

    // Create encoder and encode
    encoder::RowEncoder<Employee> enc;
    enc.Encode(emp);

    // Get the schema (automatically generated from struct)
    auto &schema = enc.GetSchema();
    std::cout << "Generated schema fields: ";
    for (const auto &name : schema.field_names()) {
      std::cout << name << " ";
    }
    std::cout << std::endl;

    // Read back from encoded row
    auto row = enc.GetWriter().ToRow();
    std::cout << "Encoded employee:" << std::endl;
    std::cout << "  name: " << row->GetString(0) << std::endl;
    std::cout << "  id: " << row->GetInt32(1) << std::endl;
    std::cout << "  salary: " << row->GetFloat(2) << std::endl;
  }
  std::cout << std::endl;

  // ============================================================================
  // Example 3: Nested Struct Encoding
  // ============================================================================
  std::cout << "--- Example 3: Nested Struct Encoding ---" << std::endl;
  {
    using namespace fory::row;

    // Create a department with nested employees
    Department dept{"Engineering",
                    {"Alice", 1000, 120000.0f}, // manager
                    {
                        {"Bob", 1001, 75000.0f},
                        {"Carol", 1002, 80000.0f},
                        {"Dave", 1003, 70000.0f},
                    }};

    // Encode using RowEncoder
    encoder::RowEncoder<Department> enc;
    enc.Encode(dept);

    auto &schema = enc.GetSchema();
    std::cout << "Department schema fields: ";
    for (const auto &name : schema.field_names()) {
      std::cout << name << " ";
    }
    std::cout << std::endl;

    // Read back
    auto row = enc.GetWriter().ToRow();
    std::cout << "Encoded department:" << std::endl;
    std::cout << "  dept_name: " << row->GetString(0) << std::endl;

    // Access nested manager struct
    auto manager_row = row->GetStruct(1);
    std::cout << "  manager: {name=" << manager_row->GetString(0)
              << ", id=" << manager_row->GetInt32(1)
              << ", salary=" << manager_row->GetFloat(2) << "}" << std::endl;

    // Access employees array
    auto employees_array = row->GetArray(2);
    std::cout << "  employees (" << employees_array->num_elements()
              << " total):" << std::endl;
    for (int i = 0; i < employees_array->num_elements(); ++i) {
      auto emp_row = employees_array->GetStruct(i);
      std::cout << "    - {name=" << emp_row->GetString(0)
                << ", id=" << emp_row->GetInt32(1)
                << ", salary=" << emp_row->GetFloat(2) << "}" << std::endl;
    }
  }
  std::cout << std::endl;

  // ============================================================================
  // Example 4: Array Encoding
  // ============================================================================
  std::cout << "--- Example 4: Array Encoding ---" << std::endl;
  {
    using namespace fory::row;

    // Encode a vector of employees
    std::vector<Employee> employees = {
        {"Eve", 2001, 65000.0f},
        {"Frank", 2002, 68000.0f},
    };

    encoder::RowEncoder<decltype(employees)> enc;
    enc.Encode(employees);

    auto &type = enc.GetType();
    std::cout << "Type name: " << type.name() << std::endl;

    // Get array data
    auto array_data = enc.GetWriter().CopyToArrayData();
    std::cout << "Encoded " << array_data->num_elements()
              << " employees:" << std::endl;

    for (int i = 0; i < array_data->num_elements(); ++i) {
      auto emp_row = array_data->GetStruct(i);
      std::cout << "  [" << i << "] {name=" << emp_row->GetString(0)
                << ", id=" << emp_row->GetInt32(1)
                << ", salary=" << emp_row->GetFloat(2) << "}" << std::endl;
    }
  }
  std::cout << std::endl;

  // ============================================================================
  // Example 5: Creating Arrays from Vectors
  // ============================================================================
  std::cout << "--- Example 5: Direct Array Creation ---" << std::endl;
  {
    using namespace fory::row;

    // Create array directly from vector
    std::vector<int32_t> numbers = {10, 20, 30, 40, 50};
    auto array = ArrayData::From(numbers);

    std::cout << "Created array with " << array->num_elements()
              << " elements:" << std::endl;
    std::cout << "  " << array->ToString() << std::endl;
  }
  std::cout << std::endl;

  std::cout << "=== All examples completed successfully! ===" << std::endl;

  return 0;
}
